package util

import (
	"container/list"
	"fmt"
	"github.com/goccy/go-json"
	"github.com/gookit/goutil/strutil"
	"hash/crc32"
	"hash/fnv"
	"math/rand"
	"net/url"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const FMT_DATE_TIME = "2006-01-02 15:04:05"
const FMT_DATE = "2006-01-02"
const FMT_DATE_PURE = "20060102"
const DEFAULT_SPACE = " "

var _CRC_TABLE = crc32.MakeTable(0xD5828281)

var REGX_STR_PURE = regexp.MustCompile(`[0-9a-zA-Z\p{Han}]+`)
var REGX_STR_CLEAN = regexp.MustCompile("[`~!@#$%^&*()\\\\_\\-+=|{}':;,\\[\\]\".<>/?！￥…（）—【】‘；：”“’。，、？\\s \n]")
var REGX_STR_CLEAN_BLANK = regexp.MustCompile("[\\s \n]")
var REGX_STR_CLEAN_VIDEO_NUM = regexp.MustCompile(`(\\"extras\\":\\".+?\\",)|(\\"forward\\":\\".+?\\",)|(\\"thumb_url\\":\\".+?\\",)`)

var SEPS = [...]string{"-", "=", "@", "#", "*", "+", ".", "●", "★", "※", "☼", "▷", "◁", "☞", "♩", "♪", "♫", "♬", "§", "❉", "❀", "¤", "۞", "€", "△", "▲", "◎", "♂", "♀", "○", "∮", "∝", "∞", "≌", "∷", "⊙", "θ", "ω", "ψ", "φ", "γ", "δ", "ζ", "ε", "ξ", "🌻", "🦈", "🔆", "ꕥ", "ঞ", "🎾", "🎗", "🍒", "🌱", "💖", "🎻", "🥤", "🏀", "🦋", "💎", "🍄", "💢", "🌷", "🌳", "🌵", "💍", "🌹", "🍀", "🍊", "🌙", "🐑", "🍓", "🎧"}

func IndexAt(s, sep string, n int) int {
	if n < 0 {
		return -1
	}
	idx := strings.Index(s[n:], sep)
	if idx > -1 {
		idx += n
	}
	return idx
}

func Unicode2Utf8(source string) string {
	var res = []string{""}
	sUnicode := strings.Split(source, "\\u")
	var context = ""
	for idx, v := range sUnicode {
		var additional = ""
		if len(v) < 1 {
			continue
		}
		if (idx == 0 && strings.Index(source, "\\u") == 0) || idx > 0 {
			if len(v) > 4 {
				rs := []rune(v)
				v = string(rs[:4])
				additional = string(rs[4:])
			}
			temp, err := strconv.ParseInt(v, 16, 32)
			if err != nil {
				context += v
			}
			context += fmt.Sprintf("%c", temp)
			context += additional
		} else {
			context += v
		}

	}
	res = append(res, context)
	return strings.Join(res, "")
}

func PureStr(str string) string {
	ss := REGX_STR_PURE.FindAllString(str, -1)
	return CleanStr(strings.Join(ss, ""))
}

func CleanStr(str string) string {
	return REGX_STR_CLEAN.ReplaceAllString(str, "")
}

func CleanBlank(str string) string {
	return REGX_STR_CLEAN_BLANK.ReplaceAllString(str, "")
}

func CleanVideoNum(str string) string {
	return REGX_STR_CLEAN_VIDEO_NUM.ReplaceAllString(str, "")
}

func CleanProductName(productName string) string {
	return strings.TrimSpace(regexp.MustCompile("回复.+?[:：]").ReplaceAllString(productName, ""))
}

func EncodeURIComponent(str string) string {
	r := url.QueryEscape(str)
	r = strings.Replace(r, "+", "%2B", -1)
	return r
}

func ToStr(x interface{}) string {
	kd := reflect.TypeOf(x).Kind()
	if kd == reflect.Int {
		return strconv.Itoa(x.(int))
	}

	if kd == reflect.Float64 {
		return formatDecimal(x.(float64))
	}

	if kd == reflect.Float32 {
		return formatDecimal(float64(x.(float32)))
	}

	if kd == reflect.Int64 {
		return strconv.FormatInt(x.(int64), 10)
	}

	if kd == reflect.String {
		return x.(string)
	}

	if kd == reflect.Bool {
		if x.(bool) {
			return "true"
		} else {
			return "false"
		}
	}

	if kd == reflect.Pointer {
		col := x.(*list.List)
		res := "{"
		for e := col.Front(); e != nil; e = e.Next() {
			res = res + ToStr(e.Value)
			if e.Next() != nil {
				res = res + ", "
			}
		}
		return res + "}"
	}

	if kd == reflect.Map {
		m := x.(map[interface{}]interface{})
		res := "{"
		for i, v := range m {
			res = res + ToStr(i) + "->" + ToStr(v) + " "
		}
		return res + "}"
	}

	return ""
}

func ToInt(v interface{}) int {
	return int(ToInt64(v))
}

func ToInt64(v interface{}) int64 {
	if s, ok := v.(int64); ok {
		return s
	}
	if s, ok := strconv.ParseInt(ToStr(v), 10, 64); ok == nil {
		return s
	}
	return strutil.Int64(ToStr(v))
}

func ToFloat(v interface{}) float64 {
	if s, ok := v.(float64); ok {
		return s
	}
	if s, ok := strconv.ParseFloat(ToStr(v), 64); ok == nil {
		return s
	}
	return 0
}

func formatDecimal(dcml float64) string {
	// truncate to 2 digits after point
	str := strconv.FormatFloat(dcml, 'f', 2, 64)
	str = string(trimFloatTrailingZeros([]byte(str)))
	return str
}

// trimFloatTrailingZeros the FormatFloat add padding zeros e.g. 20.900 to 20.9 or 10.0 to 10
// Note: the number must contain the decimal dot e.g. as result of format
func trimFloatTrailingZeros(s []byte) []byte {
	i := len(s) - 1
	for ; i >= 1; i-- {
		digit := s[i]
		if digit == '0' {
		} else {
			if digit == '.' {
				return s[:i]
			} else {
				return s[:i+1]
			}
		}
	}
	return s
}

func Hash(s string) uint32 {
	h := fnv.New32a()
	h.Write([]byte(s))
	return h.Sum32()
}

// Contains 判断obj是否在target中，target支持的类型arrary,slice,map
func Contains(srcs interface{}, tgt interface{}) bool {
	srcValue := reflect.ValueOf(srcs)
	switch reflect.TypeOf(srcs).Kind() {
	case reflect.Slice, reflect.Array:
		for i := 0; i < srcValue.Len(); i++ {
			if srcValue.Index(i).Interface() == tgt {
				return true
			}
		}
	case reflect.Map:
		if srcValue.MapIndex(reflect.ValueOf(tgt)).IsValid() {
			return true
		}
	case reflect.Pointer:
		if lit, ok := srcs.(*list.List); ok {
			for i := lit.Front(); i != nil; i = i.Next() {
				if i.Value == tgt {
					return true
				}
			}
		}
	}
	return false
}

func ContainsAny(src string, ignoreCase bool, items ...string) bool {
	if ignoreCase {
		src = strings.ToUpper(src)
	}
	for i := range items {
		if (ignoreCase && src == strings.ToUpper(items[i])) || (!ignoreCase && src == items[i]) {
			return true
		}
	}
	return false
}

func ContainsAnySub(src string, items ...string) bool {
	for i := range items {
		if strings.Contains(src, items[i]) {
			return true
		}
	}
	return false
}

func ToJson(target interface{}) string {
	bts, err := json.MarshalIndentWithOption(&target, "", "\t", json.DisableHTMLEscape(), json.DisableNormalizeUTF8())
	if err != nil {
		return ""
	}
	return string(bts)
}

func ToTime(tm string) time.Time {
	tt, _ := time.ParseInLocation(FMT_DATE_TIME, tm, time.Local)
	return tt
}

func ToTimeStr(tm time.Time) string {
	return tm.Format(FMT_DATE_TIME)
}

func ToDate(tm string) time.Time {
	tt, _ := time.ParseInLocation(FMT_DATE, tm, time.Local)
	return tt
}

func ToDateStr(tm time.Time) string {
	return tm.Format(FMT_DATE)
}

func MatchRegx(regx string, match string) []string {
	rgx := regexp.MustCompile(regx)
	return rgx.FindStringSubmatch(match)
}

/*
GenSepStr

num 建议为8
*/
func GenSepStr(num int) string {
	ary := make([]string, num)
	for i := range ary {
		ary[i] = SEPS[rand.Intn(len(SEPS))]
	}
	return strings.Join(ary, "")
}

func MaxSubMerge(items []string, sep string) string {
	if items == nil || len(items) == 0 {
		return ""
	}
	sub := MaxSub(items)
	sb := strings.Builder{}
	sb.WriteString(sub)
	sb.WriteString(sep)
	lenSub := len(sub)
	for idx, it := range items {
		sb.WriteString(it[lenSub:])
		if idx < len(items)-1 {
			sb.WriteString(sep)
		}
	}
	return sb.String()
}

func MaxSub(items []string) string {
	lenItem := len(items)
	if items == nil || lenItem == 0 {
		return ""
	}
	fi := items[0]
	//idx := strings.LastIndex(fi, ".")
	//if idx < 0 {
	idx := len(fi) - 1
	//}
	for p := idx; p >= 0; p-- {
		sub := fi[0:p]
		if AllContains(items, sub, 1) {
			return sub
		}
	}
	return ""
}

func AllContains(items []string, sub string, start int) bool {
	lenItem := len(items)
	for k := start; k < lenItem; k++ {
		if strings.Index(items[k], sub) < 0 {
			return false
		}
	}
	return true
}

func HashCRC(cont string, n int) int {
	return int(crc32.Checksum([]byte(cont), _CRC_TABLE)>>16&0x7fff) % n
}

func ClearMap(mp map[string]interface{}) {
	for k := range mp {
		delete(mp, k)
	}
}
